﻿using System;
using System.Collections.Specialized;
using System.Configuration;
using System.Configuration.Provider;
using System.Data;
using System.Web.Security;
using MySql.Data.MySqlClient;

namespace Reviewer.Security
{
    /// <summary>
    /// 
    /// </summary>
    public class RoleProvider : System.Web.Security.RoleProvider
    {
        private string connectionString;

        public override void Initialize(string name, NameValueCollection config)
        {
            if (config == null)
                throw new ArgumentNullException("config");
            
            if (String.IsNullOrEmpty(name))
                name = "ReviewerRoleProvider";
            
            if (string.IsNullOrEmpty(config["description"]))
            {
                config.Remove("description");
                config.Add("description", "$safeprojectname$ Role Provider");
            }
            
            base.Initialize(name, config);

            string connectionStringKey = config["connectionStringName"];
            if (connectionStringKey == null || connectionStringKey.Length < 1)
                throw new ProviderException("Connection name not specified");

            string temp = ConfigurationManager.ConnectionStrings[connectionStringKey].ConnectionString;
            if (temp == null || temp.Length < 1)
            {
                throw new ProviderException("Connection string not found: " + connectionStringKey);
            }
            this.connectionString = temp;

            config.Remove("connectionStringName");
            config.Remove("applicationName");
            config.Remove("description");
            
            //Ignore additional attributes;,
            //if (config.Count > 0)
            //{
            //    string attribUnrecognized = config.GetKey(0);
            //    if (!String.IsNullOrEmpty(attribUnrecognized))
            //        throw new ProviderException("Provider unrecognized attribute: " + attribUnrecognized);
            //}
        }

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope")]
        public override bool IsUserInRole(string username, string roleName)
        {
            SecUtility.CheckParameter(ref username, true, false, true, 255, "username");
            if (username.Length < 1)
                return false;
            SecUtility.CheckParameter(ref roleName, true, true, true, 255, "roleName");

            MySqlConnectionHolder holder = MySqlConnectionHelper.GetConnection(this.connectionString);
            MySqlConnection connection = holder.connectionInstance;
            try
            {
                try
                {
                    int userId = MySqlConnectionHelper.GetUserID(connection, username, false);
                    int roleId = GetRoleId(connection, roleName);

                    MySqlCommand command;

                    if (userId == 0)
                    {
                        return false;
                    }

                    if (roleId == 0)
                    {
                        return false;
                    }

                    command = new MySqlCommand(@"SELECT UserId FROM AN_UserInRole WHERE UserId = @UserId AND RoleId = @RoleId", connection);
                    command.Parameters.Add(new MySqlParameter("@UserId", userId));
                    command.Parameters.Add(new MySqlParameter("@RoleId", roleId));

                    object result = command.ExecuteScalar();

                    if (result == null || !(result is int) || ((int)result) != userId)
                        return false;
                    return true;
                }
                catch (Exception e)
                {
                    throw MySqlConnectionHelper.GetBetterException(e, holder);
                }
                finally
                {
                    holder.Close();
                }
            }
            catch
            {
                throw;
            }
        }

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope")]
        public override string[] GetRolesForUser(string username)
        {
            SecUtility.CheckParameter(ref username, true, false, true, 255, "username");
            if (username.Length < 1)
                return new string[0];

            MySqlConnectionHolder holder = MySqlConnectionHelper.GetConnection(this.connectionString);
            MySqlConnection connection = holder.connectionInstance;
            MySqlDataReader reader = null;

            try
            {
                try
                {
                    int userId = MySqlConnectionHelper.GetUserID(connection, username, false);

                    if (userId == 0)
                    {
                        return new string[0];
                    }

                    MySqlCommand command;
                    StringCollection sc = new StringCollection();
                    String[] strReturn;


                    command = new MySqlCommand(@"SELECT RoleName FROM AN_UserInRole ur, AN_Role r " +
                                                @"WHERE ur.UserId = @UserId AND ur.RoleId = r.RoleId " +
                                                @"ORDER BY RoleName",
                                               connection);
                    command.Parameters.Add(new MySqlParameter("@UserId", userId));
                    reader = command.ExecuteReader(CommandBehavior.SequentialAccess);
                    while (reader.Read())
                        sc.Add(reader.GetString(0));
                    strReturn = new String[sc.Count];
                    sc.CopyTo(strReturn, 0);
                    return strReturn;
                }
                catch (Exception e)
                {
                    throw MySqlConnectionHelper.GetBetterException(e, holder);
                }
                finally
                {
                    if (reader != null)
                        reader.Close();
                    holder.Close();
                }
            }
            catch
            {
                throw;
            }
        }

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope")]
        public override void CreateRole(string roleName)
        {
            SecUtility.CheckParameter(ref roleName, true, true, true, 255, "roleName");

            MySqlConnectionHolder holder = MySqlConnectionHelper.GetConnection(this.connectionString);
            MySqlConnection connection = holder.connectionInstance;

            try
            {
                try
                {
                    MySqlCommand command;
                    int roleId = GetRoleId(connection, roleName);

                    if (roleId != 0)
                    {
                        throw new ProviderException("Provider role already exists: " + roleName);
                    }

                    command = new MySqlCommand(@"INSERT INTO AN_Role (RoleName) VALUES (@RName)", connection);
                    command.Parameters.Add(new MySqlParameter("@RName", roleName));
                    int returnValue = command.ExecuteNonQuery();

                    if (returnValue == 1)
                        return;
                    throw new ProviderException("Unknown provider failure");
                }
                catch (Exception e)
                {
                    throw MySqlConnectionHelper.GetBetterException(e, holder);
                }
                finally
                {
                    holder.Close();
                }
            }
            catch
            {
                throw;
            }
        }

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope")]
        public override bool DeleteRole(string roleName, bool throwOnPopulatedRole)
        {
            SecUtility.CheckParameter(ref roleName, true, true, true, 255, "roleName");
            MySqlConnectionHolder holder = MySqlConnectionHelper.GetConnection(this.connectionString);
            MySqlConnection connection = holder.connectionInstance;

            try
            {
                try
                {
                    MySqlCommand command;
                    int roleId = GetRoleId(connection, roleName);

                    if (roleId == 0)
                    {
                        return false;
                    }

                    if (throwOnPopulatedRole)
                    {
                        command = new MySqlCommand(@"SELECT COUNT(*) " +
                                               @"FROM AN_UserInRole ur, AN_User u " +
                                               @"WHERE ur.RoleId = @RoleId AND ur.UserId = u.UserId",
                                            connection);

                        command.Parameters.Add(new MySqlParameter("@RoleId", roleId));
                        object num = command.ExecuteScalar();
                        if (!(num is int) || ((int)num) != 0)
                            throw new ProviderException("Role is not empty");
                    }

                    command = new MySqlCommand(@"DELETE FROM AN_Role WHERE RoleId = @RoleId", connection);
                    command.Parameters.Add(new MySqlParameter("@RoleId", roleId));
                    int returnValue = command.ExecuteNonQuery();

                    return (returnValue == 1);
                }
                catch (Exception e)
                {
                    throw MySqlConnectionHelper.GetBetterException(e, holder);
                }
                finally
                {
                    holder.Close();
                }
            }
            catch
            {
                throw;
            }
        }

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
        public override bool RoleExists(string roleName)
        {
            try
            {
                SecUtility.CheckParameter(ref roleName, true, true, true, 255, "roleName");
            }
            catch
            {
                return false;
            }

            MySqlConnectionHolder holder = MySqlConnectionHelper.GetConnection(this.connectionString);
            MySqlConnection connection = holder.connectionInstance;
            try
            {
                try
                {
                    int roleId = GetRoleId(connection, roleName);

                    return (roleId != 0);
                }
                catch (Exception e)
                {
                    throw MySqlConnectionHelper.GetBetterException(e, holder);
                }
                finally
                {
                    holder.Close();
                }
            }
            catch
            {
                throw;
            }
        }

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope")]
        public override void AddUsersToRoles(string[] usernames, string[] roleNames)
        {
            SecUtility.CheckArrayParameter(ref roleNames, true, true, true, 255, "roleNames");
            SecUtility.CheckArrayParameter(ref usernames, true, true, true, 255, "usernames");

            MySqlConnectionHolder holder = MySqlConnectionHelper.GetConnection(this.connectionString);
            MySqlConnection connection = holder.connectionInstance;

            try
            {
                try
                {
                    int[] userIds = new int[usernames.Length];
                    int[] roleIds = new int[roleNames.Length];

                    MySqlCommand command;

                    for (int iterR = 0; iterR < roleNames.Length; iterR++)
                    {
                        roleIds[iterR] = GetRoleId(connection, roleNames[iterR]);
                        if (roleIds[iterR] == 0)
                        {
                            throw new ProviderException("Provider role not found: " + roleNames[iterR]);
                        }
                    }
                    for (int iterU = 0; iterU < usernames.Length; iterU++)
                    {
                        userIds[iterU] = MySqlConnectionHelper.GetUserID(connection, usernames[iterU], false);
                    }

                    for (int iterU = 0; iterU < usernames.Length; iterU++)
                    {
                        if (userIds[iterU] == 0)
                            continue;
                        for (int iterR = 0; iterR < roleNames.Length; iterR++)
                        {
                            command = new MySqlCommand(@"SELECT UserId FROM AN_UserInRole WHERE UserId = @UserId AND RoleId = @RoleId",
                                                       connection);
                            command.Parameters.Add(new MySqlParameter("@UserId", userIds[iterU]));
                            command.Parameters.Add(new MySqlParameter("@RoleId", roleIds[iterR]));

                            object result = command.ExecuteScalar();
                            if (result != null && (result is int) && ((int)result) == userIds[iterU])
                            { // Exists!

                                throw new ProviderException("The user " + usernames[iterU] + " is already in role " + roleNames[iterR]);
                            }
                        }
                    }

                    for (int iterU = 0; iterU < usernames.Length; iterU++)
                    {
                        if (userIds[iterU] == 0)
                        {
                            userIds[iterU] = MySqlConnectionHelper.GetUserID(connection, usernames[iterU], true);
                        }
                        if (userIds[iterU] == 0)
                        {
                            throw new ProviderException("User not found: " + usernames[iterU]);
                        }
                    }
                    for (int iterU = 0; iterU < usernames.Length; iterU++)
                    {
                        for (int iterR = 0; iterR < roleNames.Length; iterR++)
                        {
                            command = new MySqlCommand(@"INSERT INTO AN_UserInRole (UserId, RoleId) VALUES(@UserId, @RoleId)",
                                                        connection);
                            command.Parameters.Add(new MySqlParameter("@UserId", userIds[iterU]));
                            command.Parameters.Add(new MySqlParameter("@RoleId", roleIds[iterR]));

                            if (command.ExecuteNonQuery() != 1)
                            {
                                throw new ProviderException("Unknown provider failure");
                            }
                        }
                    }
                }
                catch (Exception e)
                {
                    throw MySqlConnectionHelper.GetBetterException(e, holder);
                }
                finally
                {
                    holder.Close();
                }
            }
            catch
            {
                throw;
            }
        }

        public override void RemoveUsersFromRoles(string[] usernames, string[] roleNames)
        {
            SecUtility.CheckArrayParameter(ref roleNames, true, true, true, 255, "roleNames");
            SecUtility.CheckArrayParameter(ref usernames, true, true, true, 255, "usernames");

            MySqlConnectionHolder holder = MySqlConnectionHelper.GetConnection(this.connectionString);
            MySqlConnection connection = holder.connectionInstance;

            try
            {
                try
                {
                    int[] userIds = new int[usernames.Length];
                    int[] roleIds = new int[roleNames.Length];

                    for (int iterU = 0; iterU < usernames.Length; iterU++)
                    {
                        userIds[iterU] = MySqlConnectionHelper.GetUserID(connection, usernames[iterU], false);
                        if (userIds[iterU] == 0)
                        {
                            throw new ProviderException("User not found: " + usernames[iterU]);
                        }
                    }
                    for (int iterR = 0; iterR < roleNames.Length; iterR++)
                    {
                        roleIds[iterR] = GetRoleId(connection, roleNames[iterR]);
                        if (roleIds[iterR] == 0)
                        {
                            throw new ProviderException("Role not found: " + roleNames[iterR]);
                        }
                    }
                    for (int iterU = 0; iterU < usernames.Length; iterU++)
                    {
                        for (int iterR = 0; iterR < roleNames.Length; iterR++)
                        {
                            using (MySqlCommand command = new MySqlCommand(@"SELECT UserId FROM AN_UserInRole WHERE UserId = @UserId AND RoleId = @RoleId",
                                                        connection))
                            {
                                command.Parameters.Add(new MySqlParameter("@UserId", userIds[iterU]));
                                command.Parameters.Add(new MySqlParameter("@RoleId", roleIds[iterR]));

                                object result = command.ExecuteScalar();
                                if (result == null || !(result is int) || ((int)result) != userIds[iterU])
                                { // doesn't exist!

                                    throw new ProviderException("The user " + usernames[iterU] + " is already not in role " + roleNames[iterR]);
                                }
                            }
                        }
                    }

                    for (int iterU = 0; iterU < usernames.Length; iterU++)
                    {
                        for (int iterR = 0; iterR < roleNames.Length; iterR++)
                        {
                            using (MySqlCommand command = new MySqlCommand(@"DELETE FROM AN_UserInRole WHERE UserId = @UserId AND RoleId = @RoleId",
                                                        connection))
                            {
                                command.Parameters.Add(new MySqlParameter("@UserId", userIds[iterU]));
                                command.Parameters.Add(new MySqlParameter("@RoleId", roleIds[iterR]));
                                if (command.ExecuteNonQuery() != 1)
                                {
                                    throw new ProviderException("Unknown failure");
                                }
                            }
                        }
                    }
                }
                catch (Exception e)
                {
                    throw MySqlConnectionHelper.GetBetterException(e, holder);
                }
                finally
                {
                    holder.Close();
                }
            }
            catch
            {
                throw;
            }
        }

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope")]
        public override string[] GetUsersInRole(string roleName)
        {
            SecUtility.CheckParameter(ref roleName, true, true, true, 255, "roleName");
            StringCollection sc = new StringCollection();
            String[] strReturn;
            MySqlConnectionHolder holder = MySqlConnectionHelper.GetConnection(this.connectionString);
            MySqlDataReader reader = null;
            MySqlConnection connection = holder.connectionInstance;
            try
            {
                try
                {
                    int roleId = GetRoleId(connection, roleName);

                    MySqlCommand command;

                    if (roleId == 0)
                    {
                        throw new ProviderException("Role not found: " + roleName);
                    }

                    command = new MySqlCommand(@"SELECT UserName " +
                                               @"FROM AN_UserInRole ur, AN_User u " +
                                               @"WHERE ur.RoleId = @RoleId AND ur.UserId = u.UserId " +
                                               @"ORDER BY UserName",
                                            connection);

                    command.Parameters.Add(new MySqlParameter("@RoleId", roleId));
                    reader = command.ExecuteReader(CommandBehavior.SequentialAccess);
                    while (reader.Read())
                        sc.Add(reader.GetString(0));

                }
                catch (Exception e)
                {
                    throw MySqlConnectionHelper.GetBetterException(e, holder);
                }
                finally
                {
                    if (reader != null)
                        reader.Close();
                    holder.Close();
                }
            }
            catch
            {
                throw;
            }

            strReturn = new String[sc.Count];
            sc.CopyTo(strReturn, 0);
            return strReturn;
        }

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope")]
        public override string[] FindUsersInRole(string roleName, string usernameToMatch)
        {
            SecUtility.CheckParameter(ref roleName, true, true, true, 255, "roleName");
            SecUtility.CheckParameter(ref usernameToMatch, true, true, false, 255, "usernameToMatch");

            StringCollection sc = new StringCollection();

            MySqlConnectionHolder holder = MySqlConnectionHelper.GetConnection(this.connectionString);
            MySqlDataReader reader = null;
            MySqlConnection connection = holder.connectionInstance;
            try
            {
                try
                {
                    int roleId = GetRoleId(connection, roleName);

                    MySqlCommand command;

                    if (roleId == 0)
                    {
                        throw new ProviderException("Role not found " + roleName);
                    }

                    command = new MySqlCommand(@"SELECT UserName " +
                                               @"FROM AN_UserInRole ur, AN_User u " +
                                               @"WHERE ur.RoleId = @RoleId AND ur.UserId = u.UserId AND u.UserName LIKE @UserNameToMatch " +
                                               @"ORDER BY UserName", connection);

                    command.Parameters.Add(new MySqlParameter("@RoleId", roleId));
                    command.Parameters.Add(new MySqlParameter("@UserNameToMatch", usernameToMatch));
                    reader = command.ExecuteReader(CommandBehavior.SequentialAccess);
                    while (reader.Read())
                        sc.Add((string)reader.GetString(0));
                }
                catch (Exception e)
                {
                    throw MySqlConnectionHelper.GetBetterException(e, holder);
                }
                finally
                {
                    if (reader != null)
                        reader.Close();
                    holder.Close();
                }
            }
            catch
            {
                throw;
            }
            string[] allUsers = new string[sc.Count];
            sc.CopyTo(allUsers, 0);
            return allUsers;
        }

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope")]
        public override string[] GetAllRoles()
        {
            MySqlConnectionHolder holder = MySqlConnectionHelper.GetConnection(this.connectionString);
            MySqlConnection connection = holder.connectionInstance;
            MySqlDataReader reader = null;
            try
            {
                try
                {
                    MySqlCommand command;
                    StringCollection sc = new StringCollection();
                    String[] strReturn = null;

                    command = new MySqlCommand(@"SELECT RoleName FROM AN_Role ORDER BY RoleName", connection);
                    reader = command.ExecuteReader(CommandBehavior.SequentialAccess);
                    while (reader.Read())
                        sc.Add(reader.GetString(0));
                    strReturn = new String[sc.Count];
                    sc.CopyTo(strReturn, 0);
                    return strReturn;
                }
                catch (Exception e)
                {
                    throw MySqlConnectionHelper.GetBetterException(e, holder);
                }
                finally
                {
                    if (reader != null)
                        reader.Close();
                    holder.Close();
                }
            }
            catch
            {
                throw;
            }
        }

        public override string ApplicationName
        {
            get { return "ReviewerRoleProvider"; }
            set
            {
            }
        }

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope")]
        private static int GetRoleId(MySqlConnection connection, string roleName)
        {
            object result;
            MySqlCommand command;

            command = new MySqlCommand(@"SELECT RoleId FROM AN_Role WHERE RoleName = @RoleName",
                                       connection);
            command.Parameters.Add(new MySqlParameter("@RoleName", roleName));
            result = command.ExecuteScalar();
            if (result == null || !(result is int) || ((int)result) == 0)
            {
                return 0;
            }
            else
            {
                return (int)result;
            }
        }
    }
}
